﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Windows.Input;
using Moonwish.Core.Command;
using Moonwish.Core.Messaging;

namespace Moonwish.Core
{
    [SuppressMessage(
        "Microsoft.Design",
        "CA1012",
        Justification = "Constructors should remain public to allow serialization.")]
    public abstract class ViewModelBase : ObservableObject, ICleanup, IDataErrorInfo
    {
        private static bool? _isInDesignMode;
        private IMessenger _messengerInstance;

        /// <summary>
        /// Initializes a new instance of the ViewModelBase class.
        /// </summary>
        protected ViewModelBase()
            : this(null)
        {
        }

        /// <summary>
        /// 初始化参数。
        /// </summary>
        /// <param name="messenger"></param>
        protected ViewModelBase(IMessenger messenger)
        {
            MessengerInstance = messenger;

            AddCommand = new RelayCommand(Add);

            EditCommand = new RelayCommand<object>(Edit);

            DeleteCommand = new RelayCommand<object>(Delete);

            RaisePropertyChanged(() => AddCommand);

            RaisePropertyChanged(() => EditCommand);

            RaisePropertyChanged(() => DeleteCommand);
        }

        /// <summary>
        /// Gets a value indicating whether the control is in design mode
        /// (running under Blend or Visual Studio).
        /// </summary>
        [SuppressMessage(
            "Microsoft.Performance",
            "CA1822:MarkMembersAsStatic",
            Justification = "Non static member needed for data binding")]
        public bool IsInDesignMode
        {
            get
            {
                return IsInDesignModeStatic;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the control is in design mode
        /// (running in Blend or Visual Studio).
        /// </summary>
        [SuppressMessage(
            "Microsoft.Security",
            "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands",
            Justification = "The security risk here is neglectible.")]
        public static bool IsInDesignModeStatic
        {
            get
            {
                if (!_isInDesignMode.HasValue)
                {
                    _isInDesignMode = DesignerProperties.IsInDesignTool;
                }
                return _isInDesignMode.Value;
            }
        }

        /// <summary>
        /// 消息实例。
        /// </summary>
        protected IMessenger MessengerInstance
        {
            get
            {
                return _messengerInstance ?? Messenger.Default;
            }
            set
            {
                _messengerInstance = value;
            }
        }

        /// <summary>
        /// 清除消息对象。
        /// </summary>
        public virtual void Cleanup()
        {
            Messenger.Default.Unregister(this);
        }

        /// <summary>
        /// 消息广播。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="oldValue"></param>
        /// <param name="newValue"></param>
        /// <param name="propertyName"></param>
        protected virtual void Broadcast<T>(T oldValue, T newValue, string propertyName)
        {
            var message = new PropertyChangedMessage<T>(this, oldValue, newValue, propertyName);
            MessengerInstance.Send(message);
        }

        /// <summary>
        /// 属性改变通知。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName"></param>
        /// <param name="oldValue"></param>
        /// <param name="newValue"></param>
        /// <param name="broadcast"></param>
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
            Justification = "This cannot be an event")]
        protected virtual void RaisePropertyChanged<T>(string propertyName, T oldValue, T newValue, bool broadcast)
        {
            RaisePropertyChanged(propertyName);

            if (broadcast)
            {
                Broadcast(oldValue, newValue, propertyName);
            }
        }

        /// <summary>
        /// 属性改变通知。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyExpression"></param>
        /// <param name="oldValue"></param>
        /// <param name="newValue"></param>
        /// <param name="broadcast"></param>
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
            Justification = "This cannot be an event")]
        [SuppressMessage(
            "Microsoft.Design",
            "CA1006:GenericMethodsShouldProvideTypeParameter",
            Justification = "This syntax is more convenient than other alternatives.")]
        protected virtual void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression, T oldValue, T newValue, bool broadcast)
        {
            if (propertyExpression == null)
            {
                return;
            }

            var handler = PropertyChangedHandler;

            if (handler != null
                || broadcast)
            {
                var body = propertyExpression.Body as MemberExpression;

                if (handler != null)
                {
                    Debug.Assert(body != null, "body != null");
                    handler(this, new PropertyChangedEventArgs(body.Member.Name));
                }

                if (broadcast)
                {
                    Debug.Assert(body != null, "body != null");
                    Broadcast(oldValue, newValue, body.Member.Name);
                }
            }
        }

        /// <summary>
        /// 设置属性通知广播。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyExpression"></param>
        /// <param name="field"></param>
        /// <param name="newValue"></param>
        /// <param name="broadcast"></param>
        protected void Set<T>(
            Expression<Func<T>> propertyExpression,
            ref T field,
            T newValue,
            bool broadcast)
        {
            if (EqualityComparer<T>.Default.Equals(field, newValue))
            {
                return;
            }

            var oldValue = field;
            field = newValue;
            RaisePropertyChanged(propertyExpression, oldValue, field, broadcast);
        }

        /// <summary>
        /// 设置属性通知广播。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName"></param>
        /// <param name="field"></param>
        /// <param name="newValue"></param>
        /// <param name="broadcast"></param>
        protected void Set<T>(
            string propertyName,
            ref T field,
            T newValue,
            bool broadcast)
        {
            if (EqualityComparer<T>.Default.Equals(field, newValue))
            {
                return;
            }

            var oldValue = field;
            field = newValue;
            RaisePropertyChanged(propertyName, oldValue, field, broadcast);
        }

        #region 错误验证实现开始

        protected Dictionary<string, string> errors = new Dictionary<string, string>();

        public string this[string columnName]
        {
            get
            {
                if (errors.ContainsKey(columnName))
                {
                    return errors[columnName];
                }
                return string.Empty;
            }
        }

        public string Error
        {
            get { return null; }
        }

        /// <summary>
        /// 设置单个错误。
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="errorMessage"></param>
        public void SetError(string propertyName, string errorMessage)
        {
            errors[propertyName] = errorMessage;
            RaisePropertyChanged(propertyName);
        }

        /// <summary>
        /// 清除单个错误。
        /// </summary>
        /// <param name="propertyName"></param>
        private void ClearError(string propertyName)
        {
            errors.Remove(propertyName);
        }

        /// <summary>
        /// 清除所有错误。
        /// </summary>
        private void ClearAllErrors()
        {
            var properties = errors.Select(error => error.Key).ToList();
            errors.Clear();
            foreach (string property in properties)
            {
                RaisePropertyChanged(property);
            }
        }
        #endregion 错误验证实现结束

        #region 操作命令开始
        /// <summary>
        /// 新增命令。
        /// </summary>
        public ICommand AddCommand { get; private set; }

        /// <summary>
        /// 新增方法。
        /// </summary>
        protected abstract void Add();

        /// <summary>
        /// 编辑命令。
        /// </summary>
        public ICommand EditCommand { get; private set; }

        /// <summary>
        /// 编辑方法。
        /// </summary>
        protected abstract void Edit(object selectedItem);

        /// <summary>
        /// 编辑方法。
        /// </summary>
        /// <param name="selectedItem"></param>
        /// <param name="finishedCallback"></param>
        protected abstract void Edit(object selectedItem, Action<bool?> finishedCallback);

        /// <summary>
        /// 删除命令。
        /// </summary>
        public ICommand DeleteCommand { get; private set; }

        /// <summary>
        /// 删除方法。
        /// </summary>
        protected abstract void Delete(object selectedItem);

        private bool _isBusy;
        /// <summary>
        /// 页面忙状态。
        /// </summary>
        public virtual bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                if (_isBusy != value)
                {
                    _isBusy = value;

                    RaisePropertyChanged(() => IsBusy);
                }
            }
        }
        #endregion 操作命令结束
    }
}
